from kubernetes import client, config, watch
from kubernetes.client.rest import ApiException
from kubernetes.client import V1NodeList
import threading
from common.log.log_config import logger

class KubeClient:
    def __init__(self, master_url="", kube_config=""):
        # master_url = os.getenv("MASTER_URL")
        # kube_config = os.getenv("KUBECONFIG")
        if not master_url and not kube_config:
            logger.info("load kubeclient from cluster")
            config.load_incluster_config()
        if kube_config:
            logger.info("load kubeclient from kube_config:{}".format(kube_config))
            config.load_kube_config(kube_config)
        if master_url:
            logger.info("load kubeclient from master_url:{}".format(master_url))
            configuration = type.__call__(client.Configuration)
            configuration.host = master_url
            client.Configuration.set_default(configuration)

        self.corev1_client = client.CoreV1Api()
        self.crd_client = client.CustomObjectsApi()

    def get_nodes(self, label_selector="") -> V1NodeList:
        try:
            return self.corev1_client.list_node(label_selector=label_selector,
                                         timeout_seconds=60,
                                         watch=False)
        except ApiException as e:
            print("Exception when calling CoreV1Api->list_namespaced_pod: {}".format(e))
            return V1NodeList()

    def get_namespaced_pods(self, namespace="default", label_selector=""):
        try:
            api_response = self.corev1_client.list_namespaced_pod(namespace,
                                                                 label_selector=label_selector,
                                                                 timeout_seconds=60,
                                                                 watch=False)
            return api_response.items
        except ApiException as e:
            print("Exception when calling CoreV1Api->list_namespaced_pod: {}".format(e))
            return []

    def get_namespaced_crds(self, group, version, namespace, plural):
        try:
            api_response = self.crd_client.list_namespaced_custom_object(group,
                                                                        version,
                                                                        namespace,
                                                                        plural,
                                                                        watch=False)
            return api_response["items"]
        except ApiException as e:
            print("Exception when calling CoreV1Api->list_namespaced_custom_object: {}".format(e))
            return []

#通过索引key存储object
EmptyString = ""
class CacheMap:
    def __init__(self, indexer_functtion_map):
        #like {"objectKeyA": object}
        self.items_map = {}
        #like {"namespace": getObjectNamespaceFunction}
        print("CacheMap init indexer_functtion_map is {}".format(indexer_functtion_map))
        self.indexer_functtion_map = indexer_functtion_map
        #like {"namespace":{"namesapceA":{{"objectKeya":""}, "objectKeyb"}}}
        self.indices = {}
        for index_name in indexer_functtion_map:
            self.indices[index_name] = {}

    def get(self, item_key):
        return self.items_map.get(item_key, None)

    def list(self):
        return self.items_map.values()

    def list_keys(self):
        return self.items_map.keys()

    def replace(self, items_map):
        self.items_map = items_map
        for item_key in items_map:
            self.update_indices({}, items_map[item_key], item_key)

    def add(self, item_key, item_obj):
        old_obj = self.items_map.get(item_key, None)
        self.items_map[item_key] = item_obj
        self.update_indices(old_obj, item_obj, item_key)


    def index(self, index_name, item_obj):
        if not self.indexer_functtion_map.get(index_name, None):
            print("CacheMap index can not find index function by name {}".format(index_name))
            return None

        index_func = self.indexer_functtion_map[index_name]
        index_keys_list = index_func(item_obj)
        item_key_set = set()
        for index_key in index_keys_list:
            for item_key in self.indices[index_name][index_key]:
                item_key_set.add(item_key)

        find_item_objs = []
        for item_key in item_key_set:
            find_item_objs.append(self.items_map[item_key])

        return find_item_objs

    def index_keys(self, index_name, item_obj):
        if not self.indexer_functtion_map.get(index_name, None):
            print("CacheMap index_keys can not find index function by name {}".format(index_name))
            return None

        index_func = self.indexer_functtion_map[index_name]
        index_keys_list = index_func(item_obj)
        item_key_set = set()
        for index_key in index_keys_list:
            for item_key in self.indices[index_name][index_key]:
                item_key_set.add(item_key)

        find_item_keys = []
        for item_key in item_key_set:
            find_item_keys.append(item_key)

        return find_item_keys

    #return object
    def by_index(self, index_name, index_key):
        if not self.indexer_functtion_map.get(index_name, None):
            print("CacheMap by_index can not find index function by name {}".format(index_name))
            return None

        indice = self.indices[index_name]
        find_items = []
        #like index_name: namesapceA
        #like indice[index_key] is {"objA_key":"", "objB_key":""}
        for item_key in indice[index_key]:
            find_items.append(self.items_map[item_key])

        return find_items

    #return object
    def by_index_keys(self, index_name, index_key):
        if not self.indexer_functtion_map.get(index_name, None):
            print("CacheMap by_index_keys can not find index function by name {}".format(index_name))
            return None

        indice = self.indices[index_name]
        print("indice is {}".format(indice))
        find_items = []
        #like index_name: namesapceA
        #like indice[index_key] is {"objA_key":"", "objB_key":""}
        for item_key in indice[index_key]:
            find_items.append(item_key)

        return find_items

    def update(self, item_key, item_obj):
        old_obj = self.items_map.get(item_key, None)
        self.items_map[item_key] = item_obj
        self.update_indices(old_obj, item_obj, item_key)

    def delete(self, item_key):
        item_obj = self.items_map.get(item_key, None)
        if not item_obj:
            return
        self.delete_from_indices(item_obj, item_key)
        del self.items_map[item_key]

    def update_indices(self, old_obj, new_obj, item_key):
        if old_obj:
            self.delete_from_indices(old_obj, item_key)

        for index_name in self.indexer_functtion_map:
            #调用函数获取索引
            index_value_list = self.indexer_functtion_map[index_name](new_obj)
            for index_value in index_value_list:
                self.indices[index_name].setdefault(index_value, {})
                self.indices[index_name][index_value][item_key] = EmptyString

    def delete_from_indices(self, item_obj, item_key):
        for index_name in self.indexer_functtion_map:
            #调用函数获取索引
            index_value_list = self.indexer_functtion_map[index_name](item_obj)
            for index_value in index_value_list:
                self.indices[index_name].setdefault(index_value, {})
                del self.indices[index_name][index_value][item_key]
                if len(self.indices[index_name][index_value]) == 0:
                    del self.indices[index_name][index_value]

class CacheStore:
    def __init__(self, cache_map, prim_key_func):
        self.cache_map = cache_map
        self.prim_key_func = prim_key_func

    def get_prim_key(self, item_obj):
        return self.prim_key_func(item_obj)

    def add(self, item_obj):
        item_key = self.prim_key_func(item_obj)
        self.cache_map.add(item_key, item_obj)

    def update(self, item_obj):
        item_key = self.prim_key_func(item_obj)
        self.cache_map.update(item_key, item_obj)

    def delete(self, item_obj):
        item_key = self.prim_key_func(item_obj)
        self.cache_map.delete(item_key)

    def get(self, item_obj):
        item_key = self.prim_key_func(item_obj)
        return self.cache_map.get(item_key)

    def get_by_key(self, item_key):
        return self.cache_map.get(item_key)

    def list(self):
        return self.cache_map.list()

    def list_keys(self):
        return self.cache_map.list_keys()

    def index(self, index_name, item_obj):
        return self.cache_map.index(index_name, item_obj)

    def index_keys(self, index_name, item_obj):
        return self.cache_map.index_keys(index_name, item_obj)

    def by_index(self,index_name, item_obj):
        return self.cache_map.by_index(index_name, item_obj)

    def by_index_keys(self, index_name, index_key):
        return self.cache_map.by_index_keys(index_name, index_key)

    def replace(self, items_list):
        self.cache_map.replace(items_list)

def taskset_key_func(takset_obj):
    return "{}-{}".format(takset_obj["metadata"]["namespace"], takset_obj["metadata"]["name"])

#indexer function must return list
def tasket_namespace_func(taskset_obj):
    return [taskset_obj["metadata"]["namespace"]]

class CacheTaskSet:
    def __init__(self, kube_client):
        self.kube_client = kube_client
        self.taskset_cache = CacheStore(CacheMap(dict(namespace=tasket_namespace_func)),
                                        taskset_key_func)
        self.cache_taskset()


    def cache_taskset(self):
        print("----------begin cache_taskset--------")
        args = ("octopus.openi.pcl.cn", "v1alpha1", "tasksets")
        taskset_type_list = self.kube_client.crd_client.list_cluster_custom_object(*args)
        taskset_items_list = taskset_type_list["items"]
        resource_version = taskset_type_list["metadata"]["resourceVersion"]

        taskset_items = {}
        for taskset_item in taskset_items_list:
            item_key = self.taskset_cache.get_prim_key(taskset_item)
            taskset_items[item_key] = taskset_item
        self.taskset_cache.replace(taskset_items)

        def watch_takset(resource_version):
            while True:
                print("----begin to watch taskset, resourceVersion is {}".format(resource_version))
                stream = watch.Watch().stream(self.kube_client.crd_client.list_cluster_custom_object,
                                              *args,
                                              resource_version=resource_version)

                for event in stream:
                    print("receive event type: {} {}-{}".format(event["type"],
                                                                event["object"]["metadata"]["namespace"],
                                                                event["object"]["metadata"]["name"]))

                    resource_version = event["object"]["metadata"]["resourceVersion"]
                    if event["type"] == "DELETED":
                        self.taskset_cache.delete(event["object"])
                    elif event["type"] == "ADDED":
                        self.taskset_cache.add(event["object"])
                    else:
                        self.taskset_cache.update(event["object"])

        t = threading.Thread(target=watch_takset, args=(resource_version, ))
        t.setDaemon(True)
        t.start()

    def status_phase_by_key(self, tasket_key):
        item = self.taskset_cache.get_by_key(tasket_key)
        if not item:
            print("can not find taskset by key {}".format(tasket_key))
            return None
        return item["status"]["phase"]

if __name__ == "__main__":
    from pprint import pprint
    import time
    cache_task_set = CacheTaskSet(KubeClient(kube_config="kube_config"))
    while True:
        for key in cache_task_set.taskset_cache.by_index_keys("namespace", "default"):
            pprint("key:{} status:{}".format(key, cache_task_set.status_phase_by_key(key)))
        time.sleep(10)